#pragma once

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctime>
#include <cstring>
#include <cstdio>
#include <cstdarg>
#include <string>
#include <iostream>

enum _output_position
{
    CONSOLE,
    ONEFILE,
    MULTIFILE
};

enum _log_level
{
    LOG,
    WARNING,
    ERROR,
    FATAL
};

const char* LOGFILE = "log.txt";
const int STR_SIZE = 1024;

class Log
{
public:
    Log(_output_position op_pos = CONSOLE)
        :_op_pos(op_pos)
    { }

    void operator()(_log_level log_level, const char* format, ...)
    {
        std::string level_str(log_level_to_string(log_level));
        const time_t cur_timep = (time_t)time(nullptr);
        struct tm* cur_time = localtime(&cur_timep);
        char tmp_time_str[STR_SIZE]; tmp_time_str[0]= '\0';
        sprintf(tmp_time_str, "[%d-%d-%d] [%d:%d:%d]", 
            cur_time->tm_year + 1900, cur_time->tm_mon + 1, cur_time->tm_mday, \
            cur_time->tm_hour, cur_time->tm_min, cur_time->tm_sec);
        std::string time_str(tmp_time_str);

        char tmp_msg[STR_SIZE]; tmp_msg[0] = '\0';
        va_list args;
        va_start(args, format);
        vsprintf(tmp_msg, format, args);
        va_end(args);
        std::string msg_str(tmp_msg);
        std::string log_msg = level_str + "->" + time_str +  ": " + tmp_msg + "\n";
        log_msg_output(log_level, log_msg);
    }

    void clear()
    {
        if(_op_pos != CONSOLE)
        {
            int fd = open("./log/log.txt", O_CREAT | O_TRUNC | O_WRONLY, 0666);
            close(fd);
            fd = open("./log/log.txt.LOG", O_CREAT | O_TRUNC | O_WRONLY, 0666);
            close(fd);
            fd = open("./log/log.txt.WARNING", O_CREAT | O_TRUNC | O_WRONLY, 0666);
            close(fd);
            fd = open("./log/log.txt.ERROR", O_CREAT | O_TRUNC | O_WRONLY, 0666);
            close(fd);
            fd = open("./log/log.txt.FATAL", O_CREAT | O_TRUNC | O_WRONLY, 0666);
            close(fd);
        }
    }

private:
    void log_msg_output(const _log_level log_level, const std::string& log_msg)
    {
        if(_op_pos == CONSOLE) {
            std::cout << log_msg << std::endl;
        }
        else if(_op_pos == ONEFILE)
        {
            int fd = open("./log/log.txt", O_CREAT | O_APPEND | O_WRONLY, 0666);
            if(fd < 0) { perror("log_msg_output->open err"); exit(1); }
            ssize_t ret = write(fd, log_msg.c_str(), log_msg.size());
            if(ret < 0) { perror("log_msg_output->write err"); exit(1); }
            close(fd);
        }
        else if(_op_pos == MULTIFILE)
        {
            int fd = -1;
            switch(log_level)
            {
                case LOG: fd = open("./log/log.txt.LOG", O_CREAT | O_APPEND | O_WRONLY, 0666); break;
                case WARNING: fd = open("./log/log.txt.WARNING", O_CREAT | O_APPEND | O_WRONLY, 0666); break;
                case ERROR: fd = open("./log/log.txt.ERROR", O_CREAT | O_APPEND | O_WRONLY, 0666); break;
                case FATAL: fd = open("./log/log.txt.FATAL", O_CREAT | O_APPEND | O_WRONLY, 0666); break;
            }
            if(fd < 0) { perror("log_msg_output->write err"); exit(1); }
            write(fd, log_msg.c_str(), log_msg.size());
        }
        else { perror("No such level"); }
    }

    const char* log_level_to_string(_log_level log_level)
    {
        switch (log_level)
        {
            case LOG: return "LOG";
            case WARNING: return "WARNING";
            case ERROR: return "ERROR";
            case FATAL: return "FATAL";
        };
    }

private:
    _output_position _op_pos;
};